release: Bazarr+ v2.6.2 (Clockwork) - #430
Merged
Merged
Conversation
Bumps [guessit](https://github.com/guessit-io/guessit) from 3.8.0 to 4.4.0. - [Release notes](https://github.com/guessit-io/guessit/releases) - [Changelog](https://github.com/guessit-io/guessit/blob/develop/CHANGELOG.md) - [Commits](guessit-io/guessit@v3.8.0...v4.4.0) --- updated-dependencies: - dependency-name: guessit dependency-version: 4.4.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the mantine group in /frontend with 6 updates: | Package | From | To | | --- | --- | --- | | [@mantine/core](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/core) | `9.5.2` | `9.6.0` | | [@mantine/dropzone](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/dropzone) | `9.5.2` | `9.6.0` | | [@mantine/form](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/form) | `9.5.2` | `9.6.0` | | [@mantine/hooks](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/hooks) | `9.5.2` | `9.6.0` | | [@mantine/modals](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/modals) | `9.5.2` | `9.6.0` | | [@mantine/notifications](https://github.com/mantinedev/mantine/tree/HEAD/packages/@mantine/notifications) | `9.5.2` | `9.6.0` | Updates `@mantine/core` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/core) Updates `@mantine/dropzone` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/dropzone) Updates `@mantine/form` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/form) Updates `@mantine/hooks` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/hooks) Updates `@mantine/modals` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/modals) Updates `@mantine/notifications` from 9.5.2 to 9.6.0 - [Release notes](https://github.com/mantinedev/mantine/releases) - [Changelog](https://github.com/mantinedev/mantine/blob/master/CHANGELOG.md) - [Commits](https://github.com/mantinedev/mantine/commits/9.6.0/packages/@mantine/notifications) --- updated-dependencies: - dependency-name: "@mantine/core" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine - dependency-name: "@mantine/dropzone" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine - dependency-name: "@mantine/form" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine - dependency-name: "@mantine/hooks" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine - dependency-name: "@mantine/modals" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine - dependency-name: "@mantine/notifications" dependency-version: 9.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: mantine ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.51 to 2.0.52. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-version: 2.0.52 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Preserve the supplied Arr instance when the media row is unavailable, and skip ownerless history writes safely. Cover both history writers against the migrated ownership constraint. Fixes #404
Resolve matching profile targets before evaluating the score threshold. Disabled or unrelated profiles return silently, while eligible low-score sources log one skip. Fixes #405
Reuse instance badges for results whose media kind has multiple configured instances, while preserving local-ID navigation and title filtering. Fixes #397
Fix history ownership when media lookup fails
Check translation eligibility before source-score logging
Show owning instance names in global search results
Preserve provider error categories across the worker boundary
…ished jobs The AI Subtitle Translator sidecar finishes a job as "partial" when any batch fails and returns every line, untranslated positions carrying the source text. The poll loop treated that as a hard failure and returned None without reading the result, so a 286-line translation with 57 of 58 batches translated was thrown away after the credits were spent. Partial results are now saved like completed ones; the sidecar's error is logged, shown as a notification and recorded on the History entry. The same loop gave up after 30 minutes regardless of what the sidecar reported. A 1175-line movie took 35 minutes with reasoning enabled and a shrunk batch size; the sidecar completed it and Bazarr had already reported a failure. The sidecar owns per-request timeouts and moves every job to a terminal state itself, so polling now continues while it reports queued or processing (12-hour safety net) and stops only when the status endpoint has not answered for 10 minutes. The destination of a translation was resolved with the existence-only lookup get_external_subtitles_path, so with the absolute or relative subtitle-folder setting a first-time translation got None and crashed on save, for every translator; the mods tool built its output path the same way. A dedicated destination helper keeps an existing file's precedence and otherwise resolves and creates the configured folder like downloads. OpenRouter retired the config default and the documented recommendation (google/gemini-2.5-flash-preview-05-20 and google/gemini-2.5-flash-lite-preview-09-2025) along with three ids in the settings dropdown. The default is google/gemini-2.5-flash-lite now, a startup migration rewrites the retired ids to their GA successors, and the dropdown, README and guide are refreshed.
Review finding: the completed branch accepted an empty result list as a finished translation, which would save every source line under the target name and record a success in History. At least one line is required, the same rule the partial branch already applies.
Update Browserslist to the patched 4.28.7 release
Align the cryptography security floor and verify stored secrets
Scope generated sync outputs to their owning media
fix(translator): keep partial sidecar results and stop timing out finished jobs
Compare complete installed versions using packaging with a lazy pip-vendored fallback. Reject prereleases below final floors and wrong postrelease pins while preserving installed-version, bootstrap and repair behavior.
fix: align startup requirements and compare complete versions
The submodule still referenced v1.1.0 while the published image and the partial-result handling in development rely on the 1.3.1 sidecar: tolerant parsing of model replies, a batch size that recovers, and honest partial counts.
The AI Subtitle Translator sorts OpenRouter providers by throughput unless it is told otherwise, and Bazarr never told it anything: the request config carried the key, model, temperature, concurrency, batch count and reasoning, but no provider block. Every job therefore went to the fastest provider, which is often not the cheapest, and a user who typed model:floor into the model field got a slug asking for the cheapest provider next to a body asking for the fastest one. A new translator.openrouter_provider_routing setting (default throughput, which keeps the wire shape every existing install has today) is sent as config.provider.sort by both request builders, the async translate job and the manual content endpoint, through one shared helper. The Provider Routing selector in the model tuning card offers fastest, fastest with the priority tier (:nitro), cheapest, cheapest with the flex tier (:floor), lowest latency, and OpenRouter's own load balancing. The guide explains that the provider decides what a request costs; the two shortcut options need AI Subtitle Translator 1.3.4 or newer.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified change into development.
Merge the reviewed and verified upload dialog correction into development.
…or and default An AI Subtitle Translator before 1.3.4 forwards provider.sort to OpenRouter verbatim, and OpenRouter rejects nitro, floor and default there, so a user who picked one of them against an older sidecar (the pinned submodule included) would have had every translation fail. Bazarr now reads the sidecar's version from its health endpoint once per URL (cached for five minutes) and, below 1.3.4 or when the version cannot be read, sends the plain sort each value stands for: throughput for nitro and default, price for floor, with a warning in the log. Sidecars from 1.3.4 on receive the values unchanged. The guide says so.
…ing setting The guide tells users they can append the shortcut to the model id, but the sort Bazarr sent came from the selector alone. A model ending in :floor next to the default Fastest setting therefore asked OpenRouter for the cheapest provider in the slug and the fastest one in the body, which is exactly the conflict this feature exists to remove, and an older sidecar forwards both. A routing suffix on the model id now decides the sort: price for :floor, throughput for :nitro, whatever the selector says. Other variants such as :thinking are left alone. Sidecars from 1.3.4 on drop the sort for a typed shortcut anyway, so this only matters to older ones, where it is the difference between agreeing with the slug and contradicting it.
v1.3.4 adds the provider routing values behind the Provider Routing setting: price, latency, nitro, floor and default alongside throughput, with nitro and floor going out as OpenRouter's slug shortcuts and an unknown value rejected at validation instead of travelling to OpenRouter as an invalid sort. A routing shortcut typed into the model id is honoured instead of competing with the throughput default, and the reasoning and metadata lookups strip it so a suffixed slug still resolves. Supersedes the v1.3.3 bump this branch carried; v1.3.3 is included in v1.3.4.
chore(sidecar): bump ai-subtitle-translator to v1.3.4
feat(translator): provider routing setting for OpenRouter (fastest, cheapest, :nitro, :floor)
OpenRouter's :nitro and :floor are ways to route to a model, not models. Typing one into the AI Model field left the Provider Routing selector showing something else, and the selector then did nothing, so the page could read "Cheapest" while every request asked for the fastest and pricier provider. The suffixed id also never matched OpenRouter's model list, so the details card lost the cost estimate, the context window and the pricing, and the guard that disables Reasoning Mode for models without reasoning support never ran. Typing a routing shortcut now moves it into the Provider Routing selector and leaves the plain model id in the field, with a line saying so. One field owns the routing, the page cannot contradict itself, and the details resolve again. The details lookup also strips a routing suffix on its own, so a config saved before this still shows its model card. Every other variant, :free, :thinking, :online, :extended, :exacto and :batch among them, names a different model rather than a way to reach the same one, and is left on the id untouched.
Adoption ran on every keystroke, and mid-word the text can be an exact suffix of something longer. Typing "some/model:floorplan" therefore adopted :floor the moment the "r" landed, rewrote the field to "some/model", and appended the rest to produce "some/modelplan". The documented typo ":floorr" and lookalikes such as ":nitrox" corrupted the same way; the pure-function tests covered them, but the component tests pasted the value in one go and never typed through the suffix. Adoption now runs when the field is left, so a shortcut is only recognised once the user has finished typing it. Saving without leaving the field is still correct: the backend already gives a typed suffix precedence over the selector.
Four problems, all from adopting the shortcut at the wrong moment or only halfway. A keyboard save never triggered it. Ctrl+S submits the staged values straight away, so typing a shortcut and saving without leaving the field stored the suffixed model next to the old routing, and the settings contradicted each other after a reload. The shortcut now blurs the focused field and waits a tick for that change to land before submitting, which fixes every settings input that commits on blur rather than on change. A stacked id kept one shortcut. "model:nitro:floor" dropped only ":floor" and saved "model:nitro" with the selector on floor, and both the backend and the sidecar then read ":nitro" off the id and let it win. All routing shortcuts now come off and the last one typed is the one adopted. A padded id was trimmed for the lookup but not for the setting, so the details card showed a valid model while the request would have carried the padding on the slug. The normalised id is written back whether or not a shortcut was found. The notice claimed the routing was still what it adopted, even after the selector had been changed by hand. It now follows the setting.
…Ctrl+S Pressing Enter in a settings field submits the form natively, without blurring it first. An input that stages its value on blur, such as the AI Model field adopting an OpenRouter routing shortcut, therefore lost the user's last edit: the raw suffixed model was saved next to the previous routing, and the two contradicted each other after a reload. The keyboard shortcut had the same problem and was fixed on its own, which left the more common path open. Blurring and the deferred submit now sit in one place that the form's onSubmit and the shortcut both call, so the Save button, Enter and Ctrl+S behave the same.
feat(translator): adopt a routing shortcut typed into the model id
The manual translate action on a movie produced a job called "Translating EN to HU" with no film in it, and the AI Translator jobs table showed only the language pair. Episodes were fine. get_title and get_description branch on media_type, and the callers spell it two different ways: the translate endpoint sends "movie" while the batch and mass-operation paths send "movies". Only the plural was matched, so a movie fell through to the series branch, found no sonarr_series_id and returned an empty string. That emptied the job label, the Media column, and the overview the AI translator puts in its prompt, which quietly cost movies their context. Both functions now accept either spelling through one predicate, so a caller cannot lose the title again by picking the other word.
…ance radarrId and sonarrSeriesId are unique only together with arr_instance_id, so looking a title up by the upstream id alone can return a sibling instance's media. With two Radarr instances holding the same id, a translation could be labelled with the wrong film and, through get_description, hand the wrong overview to the provider as prompt context. Both helpers now take the owning instance and narrow every query with it. The owner is threaded from translate_subtitles_file, which already had it, through the factory into the translator services, so the label and the prompt describe the media the user actually asked about. Callers with no owner pass None and behave as before. The movie branch was unreachable from the manual endpoint until the media_type spelling fix in this branch, so this closes the hole that fix would otherwise have opened.
fix(translate): name the media in translation job labels for movies
…-262 fix(upload): show saved subtitles before background synchronization
Preserve later subtitle edits and deletions while background sync runs. Refresh media services after outputs are saved and keep media ownership checks when retiring generated subtitles. Report cleanup errors without losing track of a successfully saved subtitle. Stop Gemini requests and retries after cancellation and clear progress. Follow up on #428.
Render all four cards for zero counts, loading and connection errors. Show a dash for unavailable counts and preserve valid counts when only the jobs list fails.
Prepare the stability patch with subtitle publication, translation, provider handling, routing, upload and dependency corrections. - Stamp package_info with version 2.6.2. - Add 18 in-app release slides and version persistence. - Update the site version and roadmap and archive the release notes. Deploy-verified: the test instance reports 2.6.2, starts cleanly and passes all 18 release-tour slides, ten translator and six upload browser cases.
Add #429 to the complete release inventory.
Reuse a lazy ownership index within each library scan while keeping mutation checks fresh. Record successful sync promotion even when index refresh fails, and notify media consumers before sync setup can fail after an upload. Cover scan ownership scaling and both publication failure paths with regression tests.
Include the reviewed subtitle sync corrections, Gemini cancellation handling, always-visible translator status cards and release preparation. The complete candidate passed local CI and test-instance verification and was approved by the maintainer.
LavX
marked this pull request as ready for review
September 7, 2026 14:07
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release Bazarr+ v2.6.2 (Clockwork) from development. This stability patch includes immediate upload visibility with background sync, safe subtitle publication and ownership, recoverable partial translations, model routing and settings fixes, provider error handling, and dependency updates. See the archived release notes for the full inventory and upgrade notes.
Development head 83b59cf contains the reviewed corrections from #429 and exactly matches the user-approved test candidate. The proposed merge preserves all 1,436 candidate file/mode/gitlink entries and adds only the unchanged Sponsors configuration already on master. Both updated site files match the tested candidate; existing published assets are retained.
Validation:
Provider bundles use a separate release channel. Catalog promotion remains in LavX/bazarr-provider-catalog#113; updating the application alone does not install those bundles. Live provider verification limits are disclosed in the release notes.
This PR is prepared for release approval. Merging to master triggers image and site workflows. Creating and publishing v2.6.2 advances the version and latest image tags. Those publishing actions require the maintainer's final approval.